/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.ListBox.PositioningFunction;
using Xceed.Silverlight.Utils;

namespace Xceed.Silverlight.ListBox
{
  public abstract partial class PanelBase : Panel, IScrollInfo, ISupportInitialize
  {
#if WINDOWS_PHONE
    // Deceleration rate of inertia animations (in pixels / miliseconds squared)
    private const double InertiaDeceleration = 0.001d;
#endif

    #region Constructor

    internal PanelBase()
    {
      this.ScrollManager = new ScrollManager( this );
      this.ScrollManager.ActualScrollIndexChanged += this.OnScrollManagerActualScrollIndexChanged;
      this.ScrollManager.ScrollCompleted += this.OnScrollManagerScrollCompleted;

      this.EnumeratorHelper = new EnumeratorHelper();
      this.EnumeratorHelper.MoveCompleted += this.OnEnumeratorMoveCompleted;

      this.LayoutManager = new PanelLayoutManager( this );
      this.SelectionAndFocusManager = new PanelSelectionAndFocusManager( this );

#if WINDOWS_PHONE
      this.Loaded += new RoutedEventHandler( OnPanelLoaded );
#endif
    }

    #endregion

    #region ParentListBox Property

    internal ListBox ParentListBox
    {
      get
      {
        return this.GetValue( ListBox.ParentListBoxProperty ) as ListBox;
      }
    }

    internal virtual void OnParentListBoxChanged( PanelBase panel, DependencyPropertyChangedEventArgs e )
    {
    }

    #endregion

    #region PositioningFunction Property

    internal virtual PositioningFunctionBase PositioningFunction
    {
      get
      {
        return m_positioningFunction;
      }
      set
      {
        if( m_positioningFunction != value )
        {
          m_positioningFunction = value;

          if( this.IsInitialized )
          {
            this.LayoutManager.LaunchViewChangedTransition();
          }

        }
      }
    }

    #endregion

    #region InjectedGroupHeaderCount Private Property

    private int InjectedGroupHeaderCount
    {
      get;
      set;
    }

    #endregion

    #region MeasureOverrideInhibited Internal Property

    internal bool MeasureOverrideInhibited
    {
      get;
      set;
    }

    #endregion

    #region ArrangeOverrideInhibited Internal Property

    internal bool ArrangeOverrideInhibited
    {
      get;
      set;
    }

    #endregion

    #region ScrollManager Internal Property

    internal ScrollManager ScrollManager
    {
      get;
      private set;
    }

    #endregion

    #region LastNavigationKey Internal Property

    internal Key LastNavigationKey
    {
      get;
      set;
    }

    #endregion

    #region EnumeratorHelper Internal Property
    internal EnumeratorHelper EnumeratorHelper
    {
      get;
      private set;
    }
    #endregion

    #region BringLastItemToStartingPoint Property
    internal virtual bool BringLastItemToStartingPoint
    {
      get
      {
        return false;
      }
    }
    #endregion

    #region ItemOffset Property

    internal virtual double ItemOffset
    {
      get
      {
        return this.VerticalOffset;
      }
    }

    #endregion

    #region ItemsInViewport Property

    internal virtual double ItemsInViewport
    {
      get
      {
        return this.LayoutManager.ContainerDescriptions.Count;
      }
    }

    #endregion

    #region IsInitialized Property
    internal bool IsInitialized
    {
      get;
      private set;
    }
    #endregion

    #region ActualContainerSize Property
    internal Size ActualContainerSize
    {
      get
      {
        return m_actualContainerSize;
      }
      set
      {
        if( value != m_actualContainerSize )
        {
          m_actualContainerSize = value;
        }
      }
    }

    #endregion

    #region ViewportSizeInPixels Property

    internal Size ViewportSizeInPixels
    {
      get
      {
        return m_viewportSizeInPixels;
      }
      set
      {
        if( m_viewportSizeInPixels != value )
        {
          m_viewportSizeInPixels = value;
          this.UpdatePositionningFunction();
        }
      }
    }

    #endregion

    #region AllowStickyHeaders Internal Read-Only Property

    internal virtual bool AllowStickyHeaders
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region Mesure/Arrange

    protected override Size MeasureOverride( Size availableSize )
    {
      bool isInDesign = Xceed.Utils.Licensing.DesignerProperties.GetIsInDesignMode();

      if( double.IsInfinity( availableSize.Width ) || double.IsNaN( availableSize.Width ) )
      {
        if( !isInDesign )
          throw new InvalidOperationException( "An attempt was made to use the ListBox in an unconstrained environment (e.g., StackPanel without set values for the Width and Height properties)." );

        //Temporary value for design time purpose.
        availableSize.Width = 640d;
      }

      if( double.IsInfinity( availableSize.Height ) || double.IsNaN( availableSize.Height ) )
      {
        if( !isInDesign )
          throw new InvalidOperationException( "An attempt was made to use the ListBox in an unconstrained environment (e.g., StackPanel without set values for the Width and Height properties)." );

        //Temporary value for design time purpose.
        availableSize.Height = 480d;
      }

      if( this.LayoutManager.IsLayoutInhibited )
      {
        this.MeasureOverrideInhibited = true;
        return availableSize;
      }

      foreach( ListBoxContainer container in this.Children )
      {
        container.Measure( availableSize );

        // Ensure to update the CachedSize for each container
        // after a measure pass.
        if( container.Configuration != null )
        {
          if( container.Configuration.UpdateCachedSize( container ) )
            m_forceRefresh = true;
        }
      }

      // We must update the ViewportSizeInPixel before calling 
      // UpdateIScrollInfoPropertiesFromMeasureOverrideAvailableSize since 
      // it is used by some overrides of InvalidateViewportSize() and 
      // it could determine if InvalidateScrollInfo is required or not
      // e.g.: when the ListBox is resized
      Size currentAvailableSize = this.ViewportSizeInPixels;

      this.ViewportSizeInPixels = availableSize;

      bool invalidateScrollInfoRequired = 
        PanelBase.ShouldUpdateIScrollInfoFromMeasureOverrideHelper( this, availableSize, currentAvailableSize, this.GetScrollingOrientation() );

      if( invalidateScrollInfoRequired || m_forceRefresh )
      {
        if( !this.ScrollManager.IsScrollManagerActive )
        {
          this.InvalidateScrollInfo();
        }

        this.LayoutManager.UpdateViewportContent( availableSize );
      }

      m_forceRefresh = false;

      return availableSize;
    }

    protected override Size ArrangeOverride( Size finalSize )
    {
      if( this.LayoutManager.IsLayoutInhibited )
      {
        this.ArrangeOverrideInhibited = true;
        return finalSize;
      }

      foreach( ListBoxContainer container in this.Children )
      {
        container.Arrange( new Rect( new Point( 0, 0 ), new Size( finalSize.Width, container.DesiredSize.Height ) ) );
      }

      // Dirty way to clip the outer content
      RectangleGeometry clip = this.Clip as RectangleGeometry;

      if( clip == null )
        clip = new RectangleGeometry();

      clip.Rect = new Rect( PanelBase.EmptyPoint, finalSize );
      this.Clip = clip;

      return finalSize;
    }

    #endregion

    #region Viewport content

    internal void ComputeEstimatedSizeForConfig( DataPath datapath, Size viewport )
    {
      ListBox listBox = this.ParentListBox;
      ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
      ListBoxContainerConfigurationBase groupHeaderConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );
      ListBoxContainerConfigurationBase currentConfiguration;
      ListBoxContainer currentReferenceContainer;

      bool isGroupDataItem = ( datapath.LastChild is GroupDataItem );
      bool foundInDictionnary = false;

      if( isGroupDataItem )
      {
        currentConfiguration = groupHeaderConfiguration;
        currentReferenceContainer = m_referenceContainerForGroup;
      }
      else
      {
        currentConfiguration = itemConfiguration;
        currentReferenceContainer = m_referenceContainerForItem;
      }

      if( currentReferenceContainer == null )
      {
        // Find container for the dataPath
        if( this.LayoutManager.DataPathToContainerMapping.TryGetValue( datapath, out currentReferenceContainer ) )
        {
          foundInDictionnary = true;
        }

        if( currentReferenceContainer == null )
        {
          currentReferenceContainer = this.GetAvailableContainer( currentConfiguration, 0 );
        }

        // Prepare the container so it's dataTemplate is assigned
        listBox.PrepareContainerForItem( currentReferenceContainer, datapath );

        // Mesure the container so we get a rough estimated size
        currentReferenceContainer.Measure( viewport );
        currentConfiguration.UpdateCachedSize( currentReferenceContainer );

        // Recycle the container if we didn't picked it from the dictionnary
        if( !foundInDictionnary )
        {
          // Even though we do not explicitly want to defer container recycling
          // the container will be put in the waiting list if it is not elligible
          // for recycling.
          currentReferenceContainer.RecycleEvenIfFocused = true;
          this.RecycleContainerAndLayoutOutOfView( currentReferenceContainer );
        }
      }

      if( isGroupDataItem )
      {
        m_referenceContainerForGroup = currentReferenceContainer;
      }
      else
      {
        m_referenceContainerForItem = currentReferenceContainer;
      }
    }

    internal virtual double GetAverageContainerSize()
    {
      ListBoxContainerConfigurationBase itemConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.Item );
      ListBoxContainerConfigurationBase groupHeaderConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.GroupHeader );

      Size itemSize = new Size();
      Size groupSize = new Size();

      if( itemConfiguration != null )
        itemSize = itemConfiguration.CachedSize;

      if( groupHeaderConfiguration != null )
        groupSize = groupHeaderConfiguration.CachedSize;

      return ( itemSize.Height + groupSize.Height ) / 2;
    }

    internal ListBoxContainer GetAvailableContainer( ListBoxContainerConfigurationBase configuration, int zOrder )
    {
      ListBoxContainer container = this.DequeueRecycledContainer( configuration );

      if( container == null )
        container = this.CreateNewContainer( configuration, zOrder );

      return container;
    }



    internal virtual void AdjustContainerTargetPosition( ListBoxContainer container )
    {
    }

    private ListBoxContainerConfigurationBase GetContainerConfigurationFromDataPath( DataPath dataPath,
                                                                                     ListBoxContainerConfigurationBase itemConfiguration,
                                                                                     ListBoxContainerConfigurationBase groupHeaderConfiguration )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "DataPath shouldn't be null here.", "dataPath" );

      if( dataPath.LastChild is GroupDataItem )
      {
        return groupHeaderConfiguration;
      }
      else
      {
        return itemConfiguration;
      }

    }

    private void InvalidateContainerPreparation()
    {
      foreach( ListBoxContainer container in this.LayoutManager.GetLayoutedContainers( false ) )
      {
        container.InvalidatePrepare();
      }
    }

    private ListBoxContainer CreateNewContainer( ListBoxContainerConfigurationBase configuration, int zOrder )
    {
      ListBoxContainer container = configuration.CreateContainer();

      // If there's an animation running, add the container to the animation
      this.LayoutManager.AddNewContainerToCurrentTransitions( container );

      this.AddContainerToPanel( container, zOrder );

      return container;
    }

    private static int CompareDataPaths( DataPath firstDataPath, DataPath secondDataPath )
    {
      //This method will return 0 if the two node paths are equivalent 
      //This method will return a negative number that indicates how much the first node path needs to be unwinded to get to a common root with the second node path
      //This method will return a positive number if the second node path is longer (and root are equivalents).

      if( firstDataPath == null || firstDataPath.Depth == 0 )
        return 0;

      if( secondDataPath == null || secondDataPath.Depth == 0 )
        return -firstDataPath.Depth;

      int dataPathLength = firstDataPath.Depth;
      int maxValue = secondDataPath.Depth - 1;
      int matchCounter = 0;

      while( matchCounter != dataPathLength )
      {
        if( !firstDataPath[ matchCounter ].Equals( secondDataPath[ matchCounter ] ) )
          return matchCounter - dataPathLength;

        matchCounter++;
      }

      return maxValue - matchCounter;
    }

    private void UpdatePositionningFunction()
    {
      if( this.PositioningFunction != null )
      {
        this.PositioningFunction.UpdateEffectiveViewportSizeInPixels( this.ViewportSizeInPixels );
      }
    }



    #endregion

    #region Recycling



    private void AddContainerToRecyclingWaitingList( ListBoxContainer container )
    {
      Debug.Assert( container != null );
      m_recyclingWaitingList.Add( container );
    }

    private void RemoveContainerFromRecyclingWaitingList( ListBoxContainer container )
    {
      Debug.Assert( container != null );
      m_recyclingWaitingList.Remove( container );
    }

    private void ProcessRecyclingWaitingList()
    {
      List<ListBoxContainer> list = m_recyclingWaitingList.ToList<ListBoxContainer>();

      foreach( ListBoxContainer container in list )
      {
        // Even though we do not explicitly want to defer container recycling
        // the container will stay the waiting list if it is not elligible
        // for recycling.
        this.RecycleContainerAndLayoutOutOfView( container );
      }
    }

    private void RecycleContainerAndLayoutOutOfView( ListBoxContainer container )
    {
      // When we call this method in such a manner that we want the container recycled
      // as soon as possible, check the container for recycling elligibility.
      // If it does not meet the requirements, then we have no choice but to add it to the
      // waiting list and return.
      if( !container.GetIsElligibleForRecycling() )
      {
        this.AddContainerToRecyclingWaitingList( container );
        return;
      }

      this.RemoveContainerFromRecyclingWaitingList( container );

      // Remove all animations before clearing any 
      // properties to avoid race conditions
      container.RemoveAllAnimations();

      this.RecycleContainer( container );

      this.ParentListBox.ClearContainerForItem( container );

      container.AbsoluteOpacity = PanelBase.DefaultContainerOpacity;
      container.AbsoluteOffset = PanelBase.OutOfViewPoint;
      container.AbsoluteRotationAngle = 0d;
      container.AbsoluteScaleFactor = 1.0d;
      container.UpdateLayoutTransform();

      container.OnRecycledAndLayoutedOutOfView();
    }

    private void RecycleContainer( ListBoxContainer container )
    {
      Queue<ListBoxContainer> recyclingQueue = null;

      ConfigurationType configurationType = container.Configuration.ConfigurationType;

      if( !m_recycleBins.TryGetValue( configurationType, out recyclingQueue ) )
      {
        recyclingQueue = new Queue<ListBoxContainer>();
        m_recycleBins[ configurationType ] = recyclingQueue;
      }

      container.Visibility = System.Windows.Visibility.Collapsed;

      recyclingQueue.Enqueue( container );

      if( this.LayoutManager.DataPathToContainerMapping.ContainsValue( container ) )
      {
        this.LayoutManager.RemoveDataPathToContainerMapping( container.DataPath );
      }
    }

    private ListBoxContainer DequeueRecycledContainer( ListBoxContainerConfigurationBase configuration )
    {
      Queue<ListBoxContainer> recyclingQueue = null;
      ConfigurationType configurationType = configuration.ConfigurationType;

      if( !m_recycleBins.TryGetValue( configurationType, out recyclingQueue ) )
        return null;

      if( recyclingQueue.Count < 2 )
        return null;

      var container = recyclingQueue.Dequeue();

      container.Visibility = System.Windows.Visibility.Visible;

      return container;
    }

    private void ClearRecycleQueues()
    {
      foreach( Queue<ListBoxContainer> recyclingQueue in m_recycleBins.Values )
      {
        recyclingQueue.Clear();
      }

      m_recycleBins.Clear();
      m_recyclingWaitingList.Clear();
    }

    private HashSet<ListBoxContainer> m_recyclingWaitingList = new HashSet<ListBoxContainer>();

    #endregion

    #region LayoutManager Internal Property

    internal PanelLayoutManager LayoutManager
    {
      get;
      private set;
    }

    #endregion

    #region SelectionAndFocusManager Internal Property

    internal PanelSelectionAndFocusManager SelectionAndFocusManager
    {
      get;
      private set;
    }

    #endregion

    #region Internal Methods

    internal virtual bool IsContainerOutOfViewportSupported
    {
      get
      {
        return true;
      }
    }

    internal virtual IOpacityFunction GetOpacityFunction()
    {
      return null;
    }

    internal virtual IScalingFunction GetScalingFunction()
    {
      return null;
    }

    internal virtual IZIndexFunction GetZIndexFunction()
    {
      return null;
    }

    internal virtual double GetConfigurationDistanceUsedForExtent( ListBoxContainerConfigurationBase configuration )
    {
      return configuration.PrepareSize.Height;
    }

    internal virtual void ForceItemOffset( double offset )
    {
      this.ForceVerticalOffset( offset );
    }

    internal static bool ShouldUpdateIScrollInfoFromMeasureOverrideHelper(
      PanelBase panelBase,
      Size availableSize,
      Size currentAvailableSize,
      Orientation orientation )
    {
      bool invalidateScrollInfoRequired = false;

      DataSourceEnumeratorWrapper wrapper = panelBase.ParentListBox.DataSourceEnumeratorWrapper;
      long wrapperIndex = wrapper.Index;

      double newExtent = wrapper.ItemCount;

      if( orientation == Orientation.Vertical )
      {
        if( panelBase.ExtentHeight != newExtent )
        {
          panelBase.ExtentHeight = newExtent;
          invalidateScrollInfoRequired = true;
        }

        if( panelBase.ExtentWidth != availableSize.Width )
        {
          panelBase.ExtentWidth = availableSize.Width;
          invalidateScrollInfoRequired = true;
        }

        invalidateScrollInfoRequired |= ( currentAvailableSize.Height != availableSize.Height );

        // Set the ViewportWidth to the ExtentWidth
        panelBase.ViewportWidth = panelBase.ExtentWidth;

        // Force the VerticalOffset to the DataSourceEnumerator index
        // when the ScrollManager is not active
        if( !panelBase.ScrollManager.IsScrollManagerActive )
        {
          if( Math.Floor( panelBase.VerticalOffset ) != wrapperIndex )
            panelBase.ForceVerticalOffset( wrapperIndex );

          if( ( panelBase.VerticalOffset + panelBase.ViewportHeight ) > newExtent )
            panelBase.SetVerticalOffset( wrapperIndex, false );
        }
      }
      else
      {
        if( panelBase.ExtentWidth != newExtent )
        {
          panelBase.ExtentWidth = newExtent;
          invalidateScrollInfoRequired = true;
        }

        if( panelBase.ExtentHeight != availableSize.Height )
        {
          panelBase.ExtentHeight = availableSize.Height;
          invalidateScrollInfoRequired = true;
        }

        invalidateScrollInfoRequired |= ( currentAvailableSize.Width != availableSize.Width );

        // Set the ViewportHeight to the ExtentHeight
        panelBase.ViewportHeight = panelBase.ExtentHeight;

        // Force the HorizontalOffset to the DataSourceEnumerator index
        // when the ScrollManager is not active
        if( !panelBase.ScrollManager.IsScrollManagerActive )
        {
          if( Math.Floor( panelBase.HorizontalOffset ) != wrapperIndex )
            panelBase.ForceHorizontalOffset( wrapper.Index );

          if( ( panelBase.HorizontalOffset + panelBase.ViewportWidth ) > newExtent )
            panelBase.SetHorizontalOffset( wrapperIndex );
        }
      }

      return invalidateScrollInfoRequired;
    }

    internal void ProcessListBoxContainerKeyDown( ListBoxContainer container, KeyEventArgs e )
    {
      bool handleEvent = false;

      switch( e.Key )
      {
        case Key.Space:
          // Update container selection if current container is focused
          if( this.SelectionAndFocusManager.FindFocusedContainer() == container )
          {
            this.LastNavigationKey = e.Key;
            this.ParentListBox.UpdateSelection( container, SelectionAction.Keyboard );
            handleEvent = true;
          }
          break;

        case Key.End:
          this.LastNavigationKey = e.Key;
          this.HandleKeyEnd();
          handleEvent = true;
          break;

        case Key.Home:
          this.LastNavigationKey = e.Key;
          this.HandleKeyHome();
          handleEvent = true;
          break;

        case Key.PageDown:
          this.LastNavigationKey = e.Key;
          this.HandleKeyPageDown();
          handleEvent = true;
          break;

        case Key.PageUp:
          this.LastNavigationKey = e.Key;
          this.HandleKeyPageUp();
          handleEvent = true;
          break;

        case Key.Down:
          this.LastNavigationKey = e.Key;
          this.HandleKeyDown();
          handleEvent = true;
          break;

        case Key.Up:
          this.LastNavigationKey = e.Key;
          this.HandleKeyUp();
          handleEvent = true;
          break;

        case Key.Left:
          this.LastNavigationKey = e.Key;
          this.HandleKeyLeft();
          handleEvent = true;
          break;

        case Key.Right:
          this.LastNavigationKey = e.Key;
          this.HandleKeyRight();
          handleEvent = true;
          break;

        default:
          break;
      }

      if( handleEvent )
      {
        e.Handled = handleEvent;

        // We must force the InvalidateScrollInfo
        // wil processing a navigation key to ensure
        // the ScrollBar moves during e.g. a Page or Line
        // down that requires a Scroll action because 
        // the target DataPath is of View.
        m_forceInvalidateIScrollInfo = true;
        this.InvalidateScrollInfo();
      }
    }

    internal virtual void ProcessMouseWheel( MouseWheelEventArgs e )
    {
      if( e.Delta < 0 )
      {
        this.MouseWheelDown();
      }
      else
      {
        this.MouseWheelUp();
      }
    }

    internal void ProcessListBoxContainerMouseDown( ListBoxContainer container, MouseButtonEventArgs e, bool bringContainerIntoView )
    {
      if( ( container == null ) || ( container.DataPath == null ) )
        return;

      if( this.SelectionAndFocusManager.HasSelectedDataPath() && this.SelectionAndFocusManager.IsSelectedDataPath( container.DataPath ) )
      {
        this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();
      }

      if( ( container == LayoutManager.StickyHeader ) || ( !bringContainerIntoView ) )
        return;

      // We cannot bring the container fully into view if the positionning function
      // doesn't support the required functions.
      PositioningFunctionBase positioningFunction = this.PositioningFunction;
      if( ( positioningFunction == null ) || ( !positioningFunction.IsScrollOffsetDifferenceBetweenPositionsSupported ) )
        return;

      bool isScrollingBackward;

      // There is no sticky header to consider.
      if( ( !this.AllowStickyHeaders ) || ( this.LayoutManager.ContainerDescriptions.ForcedStickyContainerDescription == null ) )
      {
        int index = this.LayoutManager.ContainerDescriptions.IndexOf( container.DataPath );

        if( index == -1 )
          return;

        // The container is already fully visible.
        ContainerDescription containerDescription = this.LayoutManager.ContainerDescriptions[ index ];
        if( containerDescription.ProportionInEffectiveViewport >= 1.0d )
          return;

        // We are on the first visible container in the effective viewport if
        // the previous container is not visible at all.
        isScrollingBackward = ( index <= 0 )
                           || ( this.LayoutManager.ContainerDescriptions.Count <= 0 )
                           || ( this.LayoutManager.ContainerDescriptions[ index - 1 ].ProportionInEffectiveViewport <= 0.0d );
      }
      // We must make sure the container isn't partially masked by a sticky header.
      else
      {
        Size stickyHeaderSize = new Size( this.LayoutManager.StickyHeader.AbsoluteElementWidth,
                                          this.LayoutManager.StickyHeader.AbsoluteElementHeight );

        Point containerPosition = container.AbsoluteOffset;
        Size containerSize = new Size( container.ActualWidth, container.ActualHeight );
        Size viewport = this.ViewportSizeInPixels;

        double scrollOffsetForward = this.GetScrollOffsetToBeInBringIntoViewPosition( containerPosition,
                                                                                      containerSize,
                                                                                      stickyHeaderSize,
                                                                                      viewport,
                                                                                      true );
        double scrollOffsetBackward = this.GetScrollOffsetToBeInBringIntoViewPosition( containerPosition,
                                                                                       containerSize,
                                                                                       stickyHeaderSize,
                                                                                       viewport,
                                                                                       false );

        if( !LayoutUtils.AreClose( scrollOffsetForward, scrollOffsetBackward ) )
        {
          // The container is fully visible in the effective viewport if the scroll offsets are
          // of opposite signs.  We still need to check out if the sticky header is over some
          // part of the container.
          if( Math.Abs( Math.Sign( scrollOffsetForward ) - Math.Sign( scrollOffsetBackward ) ) >= 2 )
          {
            // The container isn't under the sticky header.  There is no need to scroll.
            if( Math.Abs( scrollOffsetForward ) + Math.Abs( scrollOffsetBackward ) >= 1.0d )
              return;
          }
        }

        // The container is exactly in the appropriate position.  There is no need to scroll.
        if( LayoutUtils.AreClose( scrollOffsetBackward, 0.0d ) )
          return;

        isScrollingBackward = ( scrollOffsetBackward < 0.0d );
      }

      Key scrollKey = ( isScrollingBackward ) ? Key.Up : Key.Down;
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( scrollKey );

      this.LayoutManager.BringContainerCompletlyIntoView( container, navigationInfo );
    }

    internal virtual void OnScrollManagerActualScrollIndexChanged( object sender, EventArgs e )
    {
      this.SetVerticalOffsetCore( this.ScrollManager.ActualScrollIndex );

      this.ScrollManager.UpdateAnimationSettings( this.ExtentHeight - this.ViewportHeight );
    }

    internal void UpdateSelectionPropertiesOnContainers()
    {
      ListBox listBox = this.ParentListBox;

      if( listBox == null )
        return;

      SelectionManager selectionManager = listBox.SelectionManager;
      IList<SelectionRange> selectedRanges = listBox.SelectedRanges;
      DataSourceWrapper dataSource = listBox.DataSourceWrapper;

      UIElementCollection children = this.Children;
      int childrenCount = children.Count;

      for( int i = 0; i < childrenCount; i++ )
      {
        ListBoxContainer container = children[ i ] as ListBoxContainer;

        DataPath dataPath = container.DataPath;

        // A null (or not loaded) DataPath means a non prepared container.
        if( ( dataPath == null ) || ( !dataPath.LastChild.IsLoaded ) )
          continue;

        SelectionRange selectedRange = null;

        if( container.IsSelectable )
        {
          selectedRange = SelectionHelper.FindSelectionRangeForPath( dataSource,
            dataPath,
            selectedRanges );
        }

        container.SelectionRange = selectedRange;
        container.IsSelected = ( selectedRange != null );
      }
    }

    internal void InitializeDataSourceEnumerator( DataSourceEnumeratorWrapper dataSourceEnumerator )
    {
      this.EnumeratorHelper.DataEnumerator = dataSourceEnumerator;
    }

    internal abstract ContainerLayoutMode GetPositioningContainerLayoutMode();

    internal virtual Orientation GetScrollingOrientation()
    {
      return Orientation.Vertical;
    }

    internal void BringItemIntoView( object item )
    {
      DataPath dataPath = item as DataPath;

      if( dataPath == null )
        dataPath = this.ParentListBox.DataSourceWrapper.CreateDataPathForObject( item );

      if( dataPath == null )
        throw new ListBoxInternalException( "Unable to create DataPath for item for BringItemIntoView." );

      this.JumpToPath( dataPath, null, GroupTarget.Current, false );
    }

    internal DependencyObject GetContainerFromItem( object item )
    {
      DataPath dataPath = item as DataPath;

      if( dataPath == null )
        dataPath = this.ParentListBox.DataSourceWrapper.CreateDataPathForObject( item );

      if( dataPath == null )
        throw new ListBoxInternalException( "Unable to create DataPath for item for GetContainerFromItem." );

      return this.LayoutManager.GetContainerFromPath( dataPath );
    }

    internal virtual AddingItemTransitionAnimation CreateAddingItemTransitionAnimation()
    {
      return new AddingItemTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal virtual RemovingItemTransitionAnimation CreateRemovingItemTransitionAnimation()
    {
      return new RemovingItemTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal virtual TransitionAnimation CreateScrollingUpTransitionAnimation()
    {
      return new ScrollingUpTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal virtual TransitionAnimation CreateScrollingDownTransitionAnimation()
    {
      return new ScrollingDownTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal void AddContainerToPanel( ListBoxContainer container, int zIndex )
    {
      Debug.Assert( !this.Children.Contains( container ) );

      // Simpliest way to do ZIndex
      if( zIndex > 0 )
      {
        this.Children.Add( container );
      }
      else
      {
        this.Children.Insert( 0, container );
      }
    }

    internal void RemoveContainerFromPanel( ListBoxContainer container )
    {
      this.Children.Remove( container );
    }

    internal void ResetPanel()
    {
      // Ensure to clear the DataEnumerator
      this.ParentListBox.ClearDataSourceEnumeratorWrapper();

      // Reset all IScrollInfo properties. Since an InvalidateMeasure is forced,
      // the measure pass will force an InvalidateScrollInfo when the other containers
      // will be set into the PanelBase.
      this.ViewportHeight = 0d;
      this.ViewportWidth = 0d;
      this.VerticalOffset = 0d;
      this.HorizontalOffset = 0d;

      // Only clear the children and the recycling bins
      // when we reset the whole panel, not while refreshing

      // Recycle the sticky header before clearing the panel's childrens
      this.LayoutManager.Reset();

      this.Children.Clear();
      this.ClearRecycleQueues();
      this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();

      this.Refresh();
    }

    internal void Refresh()
    {
      m_forceRefresh = true;

      // Set the refernece container to null so we compute a new estimated size
      m_referenceContainerForItem = null;
      m_referenceContainerForGroup = null;

      this.InvalidateContainerPreparation();
      this.InvalidateMeasure();
    }

    internal void OnDataEnumerator_DataChanged( object sender, DataChangedEventArgs e )
    {
      // Force the update of the Index and CurrentDataPath of the Enumerator
      // in a Sync DataSource when the underlying source is modified
      // by moving 0 and fetching the actual CurrentDataPath and updating
      // the actual Index of the EnumeratorHelper.
      if( e.Action != DataChangedAction.ItemsLoaded )
      {
        this.EnumeratorHelper.Move( 0 );
      }

      // If the the CurrentDataPath is updated in async source, UpdateViewportContent
      // to make sure the group headers are refreshed properly when group navigation
      // occurs.
      DataPath previouslycurrentDataPath = this.EnumeratorHelper.CurrentDataPath;

      this.EnumeratorHelper.EnsureDataSourceEnumeratorPosition();

      bool isCurrentDataPathUpdated = ( this.EnumeratorHelper.CurrentDataPath == null ) ? previouslycurrentDataPath != null : !this.EnumeratorHelper.CurrentDataPath.Equals( previouslycurrentDataPath );

      TransitionAnimation transition = null;

      if( e.Action == DataChangedAction.Add )
      {
        transition = this.CreateAddingItemTransitionAnimation();
      }
      else if( e.Action == DataChangedAction.Remove )
      {
        DataPath oldDataPath = e.OldItem as DataPath;

        // At the end of the transition, we must recycle and lay out of view
        // the removed container even if it has the focus.
        ListBoxContainer removedContainer = this.LayoutManager.GetContainerFromPath( oldDataPath );

        if( ( removedContainer != null ) && ( FocusHelper.HasFocus( removedContainer ) ) )
        {
          removedContainer.RecycleEvenIfFocused = true;
          // Do not process recycling from the get go since a transition might start for the removed container.
          this.AddContainerToRecyclingWaitingList( removedContainer );
        }

        if( ( oldDataPath != null ) && ( object.Equals( oldDataPath, this.EnumeratorHelper.CurrentDataPath ) ) )
        {
          this.EnumeratorHelper.InvalidateCurrentDataPath();
        }

        if( this.SelectionAndFocusManager.IsSelectedDataPath( oldDataPath ) )
        {
          this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();
        }

        this.EnumeratorHelper.RefreshCurrentDataPath();

        if( ( e.NewCurrentPosition != SourcePosition.BeforeFirst ) && 
            ( e.NewCurrentPosition != SourcePosition.AfterLast ) )
        {
          transition = this.CreateRemovingItemTransitionAnimation();
        }
        else
        {
          // This can occur when scrolling. Suppose that in an async source
          // we don't have any idea where we are in the data source and we scoll
          // over the first element (or after the last one). We do not want
          // to see the RemovingItemTransitionAnimation. Instead, we want to
          // see a loading animation.
          transition = new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings );
        }
      }
      else if( e.Action == DataChangedAction.ItemsLoaded )
      {
        // If UpdateItems is true, this means a full layout pass is 
        // required to avoid too many ItemsLoaded event
        // Also update viewport content if the currentDataPath has been loaded
        // in async DataSource
        if( e.UpdateItems || ( isCurrentDataPathUpdated && this.ParentListBox.IsAsyncDataSource && !this.LayoutManager.GetHasRunningTransitionAnimations() ) )
        {
          // We must call MoveHelper to ensure the value of CurrentDataPah
          // is correctly updated
          this.LayoutManager.UpdateViewportContent( this.ViewportSizeInPixels );
        }
        else
        {
          this.ProcessItemsReplace( e.LoadedItems );
        }

        this.SelectionAndFocusManager.SelectAndFocusDataPath();
      }
      else if( e.Action == DataChangedAction.Reset )
      {
        this.ResetPanel();
      }

      if( e.NewCurrentPosition != null )
      {
        // Ensure valid Position
        this.EnumeratorHelper.EnsurePositionFromSourcePosition( e.NewCurrentPosition );
      }

      if( transition != null )
      {
        this.LayoutManager.LaunchTransition( transition, this.ViewportSizeInPixels, null );
      }
    }

    internal void JumpToPath( DataPath targetDataPath, 
      NavigationInfo navigationInfo, 
      GroupTarget groupTarget, 
      bool isScrollingSensitive )
    {
      TransitionAnimation transition = this.GetScrollingAnimationFromTargetDataPath( targetDataPath, groupTarget );
      bool selectTargetContainer = false;

      if( navigationInfo == null )
      {
        // For a simple bring into view we don't need to update de selection
        // nor the focus so we simulate a dummy navigation key just
        // to jump to the DataPath
        navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.A );
        navigationInfo.ForcedBringIntoView = true;
      }
      else
      {
        // Register to the CompletedEvent so we can set the focus state on the container
        transition.Completed += new EventHandler<TransitionAnimationEventArgs>( this.OnScrollingAnimationCompleted );
        selectTargetContainer = true;
      }

      // Find out the operation to do on the source to get the target dataPath.
      long moveCount = 0;

      switch( navigationInfo.Key )
      {
        case Key.Down:
          //If on a row or collapsed group, move to following item.
          moveCount = 1;
          break;

        case Key.Up:
          //If on a row or collapsed group.
          moveCount = -1;
          break;

        case Key.PageDown:
          //Make sure we won't jump with expectedMoveCount at 0.
          moveCount = ( int )Math.Max( this.ItemsInViewport, 1d );
          break;

        case Key.PageUp:
          //Make sure we won't jump with expectedMoveCount at 0.
          moveCount = -( int )Math.Max( this.ItemsInViewport, 1d );
          break;

        default:
          break;
      }


      Size viewport = this.ViewportSizeInPixels;
      DataPath effectiveDataPath = targetDataPath;

      // Get on the target dataPath in the source.
      if( this.EnumeratorHelper.JumpMoveAndFetch( targetDataPath, moveCount ) )
      {
        effectiveDataPath = this.EnumeratorHelper.CurrentDataPath;
        double itemOffset = this.EnumeratorHelper.CurrentIndex;

        // We'll use the positioning function that gives us the position of a
        // bring into view container only if we are able to calculate the scroll
        // offset and if the starting position isn't forced be the context.
        PositioningFunctionBase positioningFunction = this.PositioningFunction;
        bool useBringIntoViewPosition = ( isScrollingSensitive ) && 
                                        ( positioningFunction != null ) &&
                                        ( positioningFunction.IsScrollOffsetDifferenceBetweenPositionsSupported );

        if( useBringIntoViewPosition )
        {
          // Find out the scrolling direction to get to the target DataPath.
          DataPath destinationDataPath = ( effectiveDataPath.LastChild.IsLoaded ) ? effectiveDataPath : targetDataPath;
          bool isTargetDataPathForward = ( this.GetTargetDataPathDirectionFromCurrentPosition( destinationDataPath, groupTarget ).GetValueOrDefault() > 0 );

          // Get the sticky header size if there's a sticky header
          Size stickyHeaderSize = this.LayoutManager.GetStickyHeaderSize();



          Size itemContainerSize = this.ParentListBox.GetContainerFactory( ConfigurationType.Item ).PrepareSize;

          positioningFunction.UpdateEffectiveViewportSizeInPixels( viewport );
          positioningFunction.PrepareLayoutPass( 0.0d, itemContainerSize, stickyHeaderSize );

          Point origin;
          double containerProportionInViewport;
          int layoutProgressionPercentage;
          double rotateAngle;

          // Find out the position of the first item that will be layouted.
          positioningFunction.PlaceContainerForward( itemContainerSize,
                                                     false,
                                                     out origin,
                                                     out containerProportionInViewport,
                                                     out layoutProgressionPercentage,
                                                     out rotateAngle );

          // Find out the distance between the first layouted item and the target container.
          double targetOffset = this.GetScrollOffsetToBeInBringIntoViewPosition( origin,
                                                                                 itemContainerSize,
                                                                                 stickyHeaderSize,
                                                                                 viewport,
                                                                                 isTargetDataPathForward );

          // Don't apply any offset if it isn't meaningful.
          if( !LayoutUtils.AreClose( targetOffset, 0.0d ) )
          {
            long moveOffset = ( long )Math.Ceiling( Math.Abs( targetOffset ) ) * Math.Sign( targetOffset );

            // We want to move on the first item of the resulting layout.  We must to a particular
            // move on the source to make sure the effective data path is tracked.  We need to do
            // this if we wish to receive the "ItemsLoaded" event from the enumerator for the
            // effective data path.  Without the "ItemsLoaded" event, we will not be able to
            // set the focus on the container on the final layout.  This, of course, only happens
            // when dummies are retrieve from the enumerator.
            this.EnumeratorHelper.Move( moveOffset, true );

            // Find out the partial offset to apply on the first layouted item to make the
            // target container fully visible.
            itemOffset = this.EnumeratorHelper.CurrentIndex + targetOffset - moveOffset;
          }
        }

        this.ForceItemOffset( itemOffset );
      }

      if( selectTargetContainer )
      {
        // Set the data path of the container we want the focus on once loaded.
        this.SelectionAndFocusManager.SetSelectedDataPath( effectiveDataPath );
      }

      this.LayoutManager.LaunchTransition( transition, viewport, null );
    }

#if WINDOWS_PHONE
    internal void InitializeManipulation()
    {
      m_manipulationStartScrollOffset = ( this.GetScrollingOrientation() == Orientation.Vertical ) ? this.VerticalOffset : this.HorizontalOffset;

      // If there is an animation running, stop it by setting current value as target value
      if( this.ScrollManager.IsScrollManagerActive )
        this.ScrollManager.ScrollToIndex( m_manipulationStartScrollOffset, 0, null, null );
    }

    internal void ApplyManipulationDelta( Point delta, Point cumulative, double containerHeight, double containerWidth )
    {
      double scrollTarget = ( this.GetScrollingOrientation() == Orientation.Vertical ) ?
        m_manipulationStartScrollOffset - ( cumulative.Y / containerHeight ) :
        m_manipulationStartScrollOffset - ( cumulative.X / containerWidth );

      this.ScrollManager.ScrollToIndex( scrollTarget, 0, null, null );
    }

    internal void CompleteManipulation( bool isInertial, ManipulationDelta totalDelta, ManipulationVelocities finalVelocities, double containerHeight, double containerWidth )
    {
      if( isInertial )
      {
        PowerEase ease = new PowerEase();
        ease.EasingMode = EasingMode.EaseOut;
        ease.Power = 3;

        // Get initial velocity (in pixels / miliseconds)
        double initialVelocity = ( ( this.GetScrollingOrientation() == Orientation.Vertical ) ?
          finalVelocities.LinearVelocity.Y : finalVelocities.LinearVelocity.X ) / 1000;

        // Get animation duration in miliseconds based on standard inertia deceleration
        // Duration is equal to initial velocity (in pixels / miliseconds) divided by deceleration (in pixels / miliseconds squared)
        double duration = Math.Abs( initialVelocity ) / InertiaDeceleration;

        // Initial velocity is the difference in distance within a very short amount of time at the start of the animation.
        // Using u as a short amount of time to use initial velocity where u = 1/1000 msec
        // Using Tdist the total distance to animate (in pixels), ease the easing function 
        // and duration the total length of the animation (in miliseconds)
        // This means the initial velocity (in pixels / miliseconds) is equal to ( Tdist * ease( u / duration ) ) / u
        double u = 1d / 1000d; //msec
        double distance = ( initialVelocity * u ) / ( ease.Ease( u / duration ) );

        double scrollTarget = ( this.GetScrollingOrientation() == Orientation.Vertical ) ?
          m_manipulationStartScrollOffset - ( ( totalDelta.Translation.Y + distance ) / containerHeight ) :
          m_manipulationStartScrollOffset - ( ( totalDelta.Translation.X + distance ) / containerWidth );


        this.ScrollManager.ScrollToIndex( scrollTarget, Convert.ToInt32( duration ), ease, null );
      }
    }
#endif

    #endregion

    #region Private Methods

    private void Initialize( Size viewportSize )
    {
      this.IsInitialized = true;
      this.LayoutManager.LaunchLoadingTransition( viewportSize );
    }

    private void UpdateIScrollInfoOffsetFromDataSourceEnumeratorIndex( long index )
    {
      if( this.ScrollManager.IsScrollManagerActive )
        return;

      double newIndex = index + ( this.ItemOffset % 1 );

      this.ForceItemOffset( newIndex );
    }

    private void OnScrollManagerScrollCompleted( object sender, ScrollCompletedEventArgs e )
    {
      m_forceInvalidateIScrollInfo = false;
      this.InvalidateScrollInfo();
    }

    private void OnEnumeratorMoveCompleted( object sender, MoveCompletedEventArgs e )
    {
      this.UpdateIScrollInfoOffsetFromDataSourceEnumeratorIndex( e.Index );
    }

    private void ProcessItemsReplace( IEnumerable<Tuple<object, object>> loadedItems )
    {
      foreach( Tuple<object, object> loadedItem in loadedItems )
      {
        this.ProcessItemReplace( loadedItem.Item1 as DataPath, loadedItem.Item2 as DataPath );
      }
    }

    private void ProcessItemReplace( DataPath oldDataPath, DataPath newDataPath )
    {
      // Update the CurrentDataPath if it is replaced
      if( ( this.EnumeratorHelper.CurrentDataPath != null )
          && ( this.EnumeratorHelper.CurrentDataPath.Depth == oldDataPath.Depth )
          && ( this.EnumeratorHelper.CurrentDataPath.LastChild == oldDataPath.LastChild ) )
      {
        this.EnumeratorHelper.CurrentDataPath = newDataPath;
      }

      ListBoxContainer stickyHeader = this.LayoutManager.StickyHeader;

      // Update the sticky header's DataPath if it is replaced
      if( ( stickyHeader != null )
          && ( stickyHeader.DataPath.Depth == oldDataPath.Depth )
          && ( stickyHeader.DataPath.LastChild == oldDataPath.LastChild ) )
      {
        DataPath GroupDataPath = newDataPath.CreateAncestorPath( Math.Max( 1, newDataPath.Depth - 1 ) );
        this.ParentListBox.PrepareContainerForItem( stickyHeader, GroupDataPath );
      }

      // Update the DataPath of the container to focus on if it is replaced.
      if( this.SelectionAndFocusManager.IsSelectedDataPath( oldDataPath ) )
      {
        this.SelectionAndFocusManager.SetSelectedDataPath( newDataPath );
      }

      // Try to find a DataPath that has the same LastChild in the DataPathToContainerMapping
      // in order to update it.
      ListBoxContainer container = null;
      DataPath oldMappedDataPath = null;

      foreach( KeyValuePair<DataPath, ListBoxContainer> keyValuePair in this.LayoutManager.DataPathToContainerMapping )
      {
        DataPath mappedDataPath = keyValuePair.Key;

        if( ( mappedDataPath.Depth == oldDataPath.Depth ) && ( mappedDataPath.LastChild == oldDataPath.LastChild ) )
        {
          oldMappedDataPath = mappedDataPath;
          container = keyValuePair.Value;
          break;
        }
      }

      // No DataPath matching the oldDataPath was found (mainly group DataPaths)
      // ignore it. The real DataPath loaded will be received later.
      if( oldMappedDataPath == null )
        return;

      Debug.Assert( container != null, "A container was not mapped to a DataPath in DataPathToContainerMapping." );

      // Update the container description when a replace occurs
      ContainerDescription containerDescription = this.LayoutManager.ContainerDescriptions[ oldMappedDataPath ];

      if( containerDescription != null )
        containerDescription.DataPath = newDataPath;

      // While processing the ItemReplace, we need to be careful and watch 
      // the focused container. We need to do a special processing if we replace a 
      // DataPath equals to the focused container DataPath to not lose the focused 
      // container (and produce a leak). If a replaced DataPath equals the focused 
      // container DataPath we DO NOT remplace the dummy DataPath with the newDataPath. 
      // We need to remove the container associate to the dummy dataPath from the 
      // DataPathToContainerMapping, apply the geometric transformations of the dummy 
      // container to the focused container, add the focused container in 
      // the DataPathToContainerMapping and finally recycle the unused dummy container.

      ListBoxContainer focusedContainer = this.SelectionAndFocusManager.FindFocusedContainer();
      DataPath focusedDataPath = null;
      bool replacingFocusedContainer = false;

      if( focusedContainer != null )
      {
        focusedDataPath = focusedContainer.DataPath;
      }

      if( focusedDataPath != null )
      {
        if( focusedDataPath.Equals( newDataPath ) )
          replacingFocusedContainer = true;
      }

      if( !replacingFocusedContainer )
      {
        // Remplace the dummy dataPath with the new DataPath
        if( this.LayoutManager.RemoveDataPathToContainerMapping( oldMappedDataPath ) )
        {
          this.ParentListBox.PrepareContainerForItem( container, newDataPath );
          this.LayoutManager.AddDataPathToContainerMapping( newDataPath, container );
        }
      }
      else
      {
        // Remove the  dummy data path from the mapping 
        this.LayoutManager.RemoveDataPathToContainerMapping( oldMappedDataPath );

        // Apply the transformations of the dummy container to 
        // the focused container so we have a fluid movement of the container
        focusedContainer.AbsoluteOpacity = container.AbsoluteOpacity;
        focusedContainer.AbsoluteScaleFactor = container.AbsoluteScaleFactor;
        focusedContainer.AbsoluteRotationAngle = container.AbsoluteRotationAngle;
        focusedContainer.MaximumHeight = container.MaximumHeight;
        focusedContainer.MaximumWidth = container.MaximumWidth;
        focusedContainer.AbsoluteOffset = container.AbsoluteOffset;
        focusedContainer.ZOrder = container.ZOrder;

        // Update the size and the position of the focused container
        focusedContainer.UpdateLayoutTransform();
        focusedContainer.UpdateSize();

        // Add the focused container to the DataPathToContainerMapping
        // because it's obviously in view (we are in the item remplace method)

        this.LayoutManager.AddDataPathToContainerMapping( newDataPath, focusedContainer );

        this.RemoveContainerFromRecyclingWaitingList( focusedContainer );

        // Recycle the unused container that was associated to the 
        // Dummy DataPath


        // Even though we do not explicitly want to defer container recycling
        // the container will be put in the waiting list if it is not elligible
        // for recycling.
        this.RecycleContainerAndLayoutOutOfView( container );
      }
    }


    private ContainerDescription PrepareStickyHeaderDescription( ContainerDescriptionList containerDescriptions )
    {
      if( this.ParentListBox.GroupDescriptions.Count() == 0 )
        return null;

      if( containerDescriptions == null )
        return null;

      ListBox listBox = this.ParentListBox;

      // Get first group in the effective viewport
      ContainerDescription firstContainerInViewport = this.GetFirstContainerDescriptionOfTheEffectiveViewport( containerDescriptions );

      if( firstContainerInViewport == null )
        return null;

      DataPath itemDataPath = firstContainerInViewport.DataPath;
      DataPath firstGroupDataPath = itemDataPath.CreateAncestorPath( Math.Max( 1, itemDataPath.Depth - 1 ) );

      // Get position of last element of this group in the viewport
      ContainerDescription lastContainerDescriptionForGroup = this.GetLastContainerDescriptionForAGroup( firstGroupDataPath, containerDescriptions );
      ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
      ListBoxContainerConfigurationBase groupConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );

      // Create and fill the sticky header container description
      ContainerDescription stickyDescription = new ContainerDescription();
      double angle = 0.0d;
      stickyDescription.TargetPosition =
        this.PositioningFunction.PlaceStickyHeader( lastContainerDescriptionForGroup.TargetPosition, groupConfiguration.PrepareSize, out angle );
      stickyDescription.ZIndex = ZIndexFunction.MaxZIndex;
      stickyDescription.Configuration = groupConfiguration;
      if( itemDataPath.LastChild.IsLoaded )
      {
        stickyDescription.DataPath = firstGroupDataPath;
      }
      else
      {
        stickyDescription.DataPath = itemDataPath;
      }
      stickyDescription.RotateAngle = angle;
      stickyDescription.ScalingFactor = 1.0;
      stickyDescription.OpacityFactor = 1.0;
      stickyDescription.ProportionInEffectiveViewport = 1.0;

      containerDescriptions.ForcedStickyContainerDescription = stickyDescription;
      return stickyDescription;
    }

    // This method looks for the last container of a specified group in the effective viewport.
    // This method assume that the container descriptions (the list) is ordered from the first container
    // in the viewport to the last one.
    private ContainerDescription GetLastContainerDescriptionForAGroup( DataPath groupDataPath, ContainerDescriptionList containerDescriptions )
    {
      DataPath containerDataPath = null;
      DataPath groupContainerDataPath = null;
      ContainerDescription lastContainerForAGroup = null;
      foreach( ContainerDescription containerDescription in containerDescriptions )
      {
        // If the container isn't in the viewport, go to the next one
        if( containerDescription.ProportionInEffectiveViewport <= 0.0d )
          continue;

        containerDataPath = containerDescription.DataPath;
        groupContainerDataPath = containerDataPath.CreateAncestorPath( Math.Max( 1, containerDataPath.Depth - 1 ) );

        if( groupContainerDataPath.Equals( groupDataPath ) )
        {
          lastContainerForAGroup = containerDescription;
        }
      }

      return lastContainerForAGroup;
    }

    // This method looks for the first container of the effective viewport.
    // This method assume that the container descriptions (the list) is ordered from the first container
    // in the viewport to the last one.
    private ContainerDescription GetFirstContainerDescriptionOfTheEffectiveViewport( ContainerDescriptionList containerDescriptions )
    {
      foreach( ContainerDescription containerDescription in containerDescriptions )
      {
        if( containerDescription.DataPath.LastChild is GroupDataItem )
          continue;

        if( containerDescription.ProportionInEffectiveViewport > 0.0d )
          return containerDescription;
      }

      return null;
    }



    // This methode looks if there is a group break between
    // the previous DataPath and the current DataPath.
    private bool IsCurrentDataPathAGroupBreak()
    {
      bool hasGroupDescriptions = this.ParentListBox.GroupDescriptions.Count > 0;

      if( !hasGroupDescriptions )
        return false;

      DataPath currentDataPath = this.EnumeratorHelper.CurrentDataPath;
      DataPath previousDataPath = null;
      bool isFirstSourceItem = false;
      bool areGroupDifferent = false;

      if( this.EnumeratorHelper.Move( -1 ) )
      {
        // There is a previous dataPath.
        previousDataPath = this.EnumeratorHelper.CurrentDataPath;
        areGroupDifferent = ( PanelBase.CompareDataPaths( previousDataPath, currentDataPath ) < -1 );
      }
      else
      {
        // There is no previous dataPath, we are at the beginning of the source.
        isFirstSourceItem = true;
      }
      // Replace the enumerator
      this.EnumeratorHelper.Move( 1 );

      return areGroupDifferent || isFirstSourceItem;
    }

    private double GetScrollOffsetToBeInBringIntoViewPosition( 
      Point originalPosition,
      Size itemContainerSize, 
      Size groupHeaderContainerSize, 
      Size viewportSizeInPixels,
      bool forwardScrolling )
    {
      Point bringIntoViewPosition =
        this.PositioningFunction.GetBringIntoViewPositionForContainer( groupHeaderContainerSize,
                                                                       itemContainerSize,
                                                                       viewportSizeInPixels,
                                                                       forwardScrolling );
      double scrollOffset =
        this.PositioningFunction.GetScrollOffsetDifferenceBetweenPositions( originalPosition,
                                                                            bringIntoViewPosition,
                                                                            itemContainerSize );

      if( LayoutUtils.AreClose( scrollOffset, 0.0d ) )
        return 0.0d;

      return scrollOffset;
    }


    #endregion

    #region KeyboardNavigation Methods

    private void HandleKeyUp()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Up );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyDown()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Down );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyLeft()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Left );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyRight()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Right );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyHome()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Home );
      this.MoveEndOrHome( navigationInfo );
    }

    private void HandleKeyEnd()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.End );
      this.MoveEndOrHome( navigationInfo );
    }

    private void HandleKeyPageUp()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.PageUp );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyPageDown()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.PageDown );
      this.MoveUpDownLeftRight( navigationInfo );
    }



    private DataPath GetDataPathAccordingToNavigationInfo( DataPath currentPath,
      NavigationInfo navigationInfo )
    {
      ListBoxContainer currentContainer = this.LayoutManager.GetContainerFromPath( currentPath );

      // Ensure the container is at least partially visible
      // before returning the first or last fully visible
      // container in the viewport. If not, we want to bring
      // into view the current container and process navigation
      // from this container.
      if( this.LayoutManager.IsContainerOutOfViewport( currentContainer, true ) )
        return null;

      if( navigationInfo.IsPageScrolling )
      {
        return this.LayoutManager.GetFirstOrLastFullyVisibleDataPathForCurrentDataPath( currentPath,
           navigationInfo );
      }
      else
      {
        return this.LayoutManager.GetNextOrPreviousDataPathForCurrentDataPath( currentPath,
         navigationInfo );
      }
    }

    private void OnNavigationScrollManagerScrollCompleted( object sender, ScrollCompletedEventArgs e )
    {
      m_scrollManagerCompletedRegistered = false;
      this.ScrollManager.ScrollCompleted -= this.OnNavigationScrollManagerScrollCompleted;

      // If selection has not changed during transition, Focus or Select Container
      DataPath focusedDataPath = this.SelectionAndFocusManager.GetFocusedOrCurrentDataPath();
      if( ( ( m_scrollStartSelectedDataPath != null ) && ( m_scrollStartSelectedDataPath.Equals( focusedDataPath ) ) ) ||
        ( ( m_scrollStartSelectedDataPath == null ) && ( focusedDataPath == null ) ) )
      {
        this.SelectionAndFocusManager.BeginFocusOrSelectContainerAfterScrollForcedForNavigation( ( e.NavigationAction != null ) ? e.NavigationAction.NavigationInfo : null );
      }

      m_scrollStartSelectedDataPath = null;
    }

    private void MoveUpDownLeftRight( NavigationInfo navigationInfo )
    {
      // We must have a current or focused DataPath in order to start
      // keyboard navigation

      DataPath currentOrFocusedPath = this.SelectionAndFocusManager.GetFocusedOrCurrentDataPath();

      if( currentOrFocusedPath == null )
        return;

      ListBox listBox = this.ParentListBox;

      bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;
      bool isCtrlDown = navigationInfo.IsCtrlDown;

      // Ensure to clear selection for all containers before
      // processing any navigation since only one item will
      // be selected at the end of this operation if the Shift
      // key is not pressed
      if( ( listBox.SelectionMode == SelectionMode.Extended ) && !isCtrlDown && !isShiftDown )
        listBox.SelectionManager.UnselectAllHelper();

      double scrollManagerTargetIndex = this.ScrollManager.TargetIndex;

      ListBoxContainer currentOrFocusedPathContainer = this.LayoutManager.GetContainerFromPath( currentOrFocusedPath, true );

      bool isCurrentNavigationActionBringIntoView = ( this.ScrollManager.ActiveNavigationAction != null ) && 
        ( this.ScrollManager.ActiveNavigationAction is BringContainerCompletelyIntoViewNavigationAction );

      // No container realized for the current DataPath or the ScrollManager is currently active, except for a BringIntoView
      if( ( currentOrFocusedPathContainer == null ) ||
        ( ( this.ScrollManager.IsScrollManagerActive ) && !isCurrentNavigationActionBringIntoView ) )
      {
        this.ProcessUpDownLeftRightForNonRealizedContainerOrWhileScrolling( scrollManagerTargetIndex, navigationInfo );
        return;
      }

      if( isCurrentNavigationActionBringIntoView )
      {
        BringContainerCompletelyIntoViewNavigationAction bringIntoViewNavigationAction = this.ScrollManager.ActiveNavigationAction as BringContainerCompletelyIntoViewNavigationAction;
        currentOrFocusedPath = bringIntoViewNavigationAction.TargetDataPath;
        currentOrFocusedPathContainer = this.LayoutManager.GetContainerFromPath( currentOrFocusedPath, true );
      }

      if( ( currentOrFocusedPathContainer != null )
          && ( listBox.SelectionMode != SelectionMode.Multiple ) )
      {
        // Ensure to reset the Selected ans Focused states
        // of the previously current container in case scrolling
        // is required. This will prevent the old Selected and Focused
        // container to keep this state while it changes during a 
        // scrolling animation.
        if( !navigationInfo.IsCtrlDown )
          currentOrFocusedPathContainer.IsSelected = false;

        currentOrFocusedPathContainer.IsVisuallyFocused = false;
      }

      bool selectAndFocusOrScrollToDataPathDone =
        this.SelectAndFocusOrScrollToDataPathAccordingToNavigationInfo( currentOrFocusedPath, navigationInfo );

      if( selectAndFocusOrScrollToDataPathDone )
        return;

      // This will force a Scroll operation and all the process
      // to Select and Focus will be done in the OnNavigationScrollManagerScrollCompleted
      // callback
      this.ForceScrollToBringContainerIntoViewAndProcessNavigation( currentOrFocusedPathContainer,
        navigationInfo );
    }

    private void ForceScrollToBringContainerIntoViewAndProcessNavigation(
      ListBoxContainer currentOrFocusedPathContainer,
      NavigationInfo navigationInfo )
    {
      // If the currentOrFocusedDataPath is not null, we need to jump to this
      // DataPath and move according to navigation
      if( ( currentOrFocusedPathContainer != null )
        && ( ( currentOrFocusedPathContainer.Translation.Y == PanelBase.OutOfViewOffset )
           || ( currentOrFocusedPathContainer.Translation.X == PanelBase.OutOfViewOffset ) ) )
      {
        navigationInfo.ForcedBringIntoView = true;
        this.JumpToPath( currentOrFocusedPathContainer.DataPath, navigationInfo, GroupTarget.Current, true );
      }
      else
      {
        double scrollManagerIndex = this.ScrollManager.TargetIndex;
        ScrollToIndexNavigationAction navigationAction = new ScrollToIndexNavigationAction( scrollManagerIndex, this, navigationInfo );
        double targetDataPathScrollIndex = navigationAction.CalculateTargetScrollIndex();

        // Ensure to focus or select the currentOrFocusedPathContainer and
        // affect the IsSelected and IsVisuallyFocused property that
        // were reset before the Move* operation since no GotFocus
        // will be received for this container neither any selection change
        if( scrollManagerIndex == targetDataPathScrollIndex )
        {
          this.SelectionAndFocusManager.FocusOrSelectContainerAfterScrollForcedForNavigation( navigationInfo );

          if( ( currentOrFocusedPathContainer != null )
            && ( this.ParentListBox.SelectionMode != SelectionMode.Multiple ) )
          {
            // Ensure to reset the Selected ans Focused states
            // of the previously current container in case scrolling
            // is required. This will prevent the old Selected and Focused
            // container to keep this state while it changes during a 
            // scrolling animation.
            if( !navigationInfo.IsCtrlDown )
              currentOrFocusedPathContainer.IsSelected = true;

            currentOrFocusedPathContainer.IsVisuallyFocused = true;
          }
        }
        else
        {
          this.StartScrollingAnimation( targetDataPathScrollIndex, navigationInfo.IsPageScrolling, navigationAction );
        }
      }
    }

    private TransitionAnimation GetScrollingAnimationFromTargetDataPath( DataPath targetDataPath, GroupTarget groupTarget )
    {
      int compare = this.GetTargetDataPathDirectionFromCurrentPosition( targetDataPath, groupTarget ).GetValueOrDefault();

      if( compare < 0 )
      {
        return this.CreateScrollingUpTransitionAnimation();
      }
      else if( compare > 0 )
      {
        return this.CreateScrollingDownTransitionAnimation();
      }
      else
      {
        return new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings );
      }
    }

    private Nullable<int> GetTargetDataPathDirectionFromCurrentPosition( DataPath targetDataPath, GroupTarget groupTarget )
    {
      // We don't have a clue where is located the target dataPath.
      if( targetDataPath == null )
        return null;

      // If the target dataPath isn't loaded, we use the GroupTarget to find the 
      // scrolling animation.
      if( !targetDataPath.LastChild.IsLoaded )
      {
        if( groupTarget == GroupTarget.Previous )
        {
          return -1;
        }
        else if( groupTarget == GroupTarget.Next )
        {
          return 1;
        }

        // We don't know where is located the target dataPath.
        return null;
      }

      // Compare the two DataPath to figure out the scrolling direction

      DataPath originalDataPath = this.LayoutManager.GetFirstFullyVisibleDataPath();
      if( originalDataPath == null )
      {
        originalDataPath = this.EnumeratorHelper.CurrentDataPath;
      }

      if( !originalDataPath.LastChild.IsLoaded )
        return null;

      return this.ParentListBox.DataSourceWrapper.DataHelper.Compare( targetDataPath, originalDataPath );
    }

    private void OnScrollingAnimationCompleted( object sender, TransitionAnimationEventArgs e )
    {
      TransitionAnimation transitionAnimation = sender as TransitionAnimation;
      transitionAnimation.Completed -= this.OnScrollingAnimationCompleted;

      this.SelectionAndFocusManager.BeginSelectAndFocusDataPath();
    }

    private bool SelectAndFocusOrScrollToDataPathAccordingToNavigationInfo( DataPath currentOrFocusedPath, NavigationInfo navigationInfo )
    {
      bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;
      bool isCtrlDown = navigationInfo.IsCtrlDown;

      // Try to directly move to the desired DataPath. It will be null
      // if the currentOrFocusedPath is out of viewport to ensure
      // a bring into view will be queued before the selection is processed.
      DataPath targetDataPath = this.GetDataPathAccordingToNavigationInfo( currentOrFocusedPath,
        navigationInfo );

      ListBoxContainer targetContainer = null;

      if( ( targetDataPath != null ) && targetDataPath.LastChild.IsLoaded )
      {
        this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();

        this.ParentListBox.CurrentDataPath = targetDataPath;

        targetContainer = this.LayoutManager.GetContainerFromPath( targetDataPath, true );

        if( targetContainer != null )
        {
          // Here, we can be sure that the container is in the realized items.
          // So what we want to do is move this container at the top of the layout by scrolling
          // to the right target index of the scroll manager.
          if( this.LayoutManager.IsContainerOutOfViewport( targetContainer ) )
          {
            if( this.LayoutManager.BringContainerCompletlyIntoView( targetContainer, navigationInfo ) )
              return true;

            targetContainer = null;
          }

          //The container was found and is fully
          //into view, nothing to do
          if( targetContainer != null )
          {
            if( !isCtrlDown || isShiftDown )
              this.ParentListBox.UpdateSelection( targetContainer, SelectionAction.Navigation );

            targetContainer.Focus();
            this.ProcessRecyclingWaitingList();
            return true;
          }
        }
      }

      return false;
    }



    private void ProcessUpDownLeftRightForNonRealizedContainerOrWhileScrolling( double scrollManagerTargetIndex, NavigationInfo navigationInfo )
    {
      // Use the TargetIndex at which the ScrollManager is positionned to calculate the next index to scroll to
      // since it can be currently active.
      ScrollToIndexNavigationAction navigationAction = new ScrollToIndexNavigationAction( scrollManagerTargetIndex, this, navigationInfo );
      double newTargetScrollIndex = navigationAction.CalculateTargetScrollIndex();
      bool isForwardScrolling = navigationInfo.IsForwardScrolling;

      if( ( isForwardScrolling && ( newTargetScrollIndex > scrollManagerTargetIndex ) )
          || ( !isForwardScrolling && ( newTargetScrollIndex < scrollManagerTargetIndex ) ) )
      {
        this.StartScrollingAnimation( newTargetScrollIndex, navigationInfo.IsPageScrolling, navigationAction );
      }
      else
      {
        // Only update the state of the ScrollManager in case the scrolling behavior
        // was not modified

        if( this.ScrollManager.ActiveNavigationAction != null )
        {
          this.ScrollManager.ActiveNavigationAction.NavigationInfo = navigationInfo;
        }
      }

    }

    private void MoveEndOrHome( NavigationInfo navigationInfo )
    {
      bool isCtrlDown = navigationInfo.IsCtrlDown;

      // Ensure to clear selection for all containers before
      // processing any navigation since only one item will
      // be selected at the end of this operation
      if( !isCtrlDown )
        this.ParentListBox.SelectionManager.UnselectAllHelper();

      MoveEndOrHomeNavigationAction navigationAction = new MoveEndOrHomeNavigationAction( this, navigationInfo );
      double targetIndex = navigationAction.CalculateTargetScrollIndex();

      double scrollManagerTargetIndex = this.ScrollManager.TargetIndex;
      bool scrollRequired = navigationInfo.IsForwardScrolling ? ( scrollManagerTargetIndex < targetIndex ) : ( scrollManagerTargetIndex != 0 );

      if( scrollRequired )
      {
        this.StartScrollingAnimation( targetIndex, navigationInfo.IsPageScrolling, navigationAction );
      }
      else
      {
        this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();

        DataPath navigatedDatapath = null;

        if( navigationInfo.IsForwardScrolling )
        {
          navigatedDatapath = this.LayoutManager.GetLastFullyVisibleDataPath();
        }
        else
        {
          navigatedDatapath = this.LayoutManager.GetFirstFullyVisibleDataPath();
        }

        // If there are not fully visible DataPath in the viewport,
        // do not attempt to get a container for it
        if( navigatedDatapath == null )
          return;

        ListBoxContainer container = this.LayoutManager.GetContainerFromPath( navigatedDatapath );

        if( !isCtrlDown || ( isCtrlDown && ( ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift ) ) )
        {
          this.ParentListBox.UpdateSelection( container, SelectionAction.Navigation );
        }

        if( container != null )
        {
          container.Focus();
          this.ProcessRecyclingWaitingList();
        }
      }
    }

    private void StartScrollingAnimation( double targetIndex, bool isPageScrolling, PanelNavigationActionBase navigationAction )
    {
      ListBox listBox = this.ParentListBox;
      int duration = 0;
      IEasingFunction easingFunction = null;

      if( isPageScrolling )
      {
        duration = listBox.AnimationSettings.GetPageScrollingDuration();
        easingFunction = listBox.AnimationSettings.GetPageScrollingEasingFunction();
      }
      else
      {
        duration = listBox.AnimationSettings.GetLineScrollingDuration();
        easingFunction = listBox.AnimationSettings.GetLineScrollingEasingFunction();
      }

      //Subscribe to the ScrollCompleted event so we can set the current element appropiately in the event handler
      if( !m_scrollManagerCompletedRegistered )
      {
        m_scrollStartSelectedDataPath = this.SelectionAndFocusManager.GetFocusedOrCurrentDataPath();
        this.ScrollManager.ScrollCompleted += this.OnNavigationScrollManagerScrollCompleted;
        m_scrollManagerCompletedRegistered = true;
      }

      this.ScrollManager.ScrollToIndex( targetIndex, duration, easingFunction, navigationAction );
    }

    private double GetGroupHeaderProportion()
    {
      if( this.PositioningFunction != null )
      {
        double headerProportion = this.PositioningFunction.GetGroupHeaderProportion();
        return headerProportion;
      }

      return 0.0d;
    }

    #endregion

    #region Private Fields

    private static double LineSize = 1;
#if !WINDOWS_PHONE
    private static double WheelSize = SystemParameters.WheelScrollLines * StackPanel.LineSize;
#else
    // Constant multiplier because parameter does no exist in WP7
    private static double WheelSize = 3 * StackPanel.LineSize;
#endif
    private static Point EmptyPoint = new Point( 0d, 0d );
    private static double OutOfViewOffset = -9999d;
    private static Point OutOfViewPoint = new Point( -9999d, -9999d );
    private static double DefaultContainerOpacity = 0.0d;


    private Size m_viewportSizeInPixels = Size.Empty;
    private Size m_extent;
    private Size m_viewport;
    private Point m_offset;
    private ScrollViewer m_scollOwner;
    private Size m_actualContainerSize = new Size( 0d, 0d );
    private int m_iSupportInitializeCount = 0;
    private double m_itemsInViewport = 0.0d;

    private bool m_forceInvalidateIScrollInfo;

    private bool m_scrollManagerCompletedRegistered;
    private PositioningFunctionBase m_positioningFunction = null;

    private Dictionary<object, Queue<ListBoxContainer>> m_recycleBins =
      new Dictionary<object, Queue<ListBoxContainer>>();


    private bool m_forceRefresh;
    private ListBoxContainer m_referenceContainerForItem = null;
    private ListBoxContainer m_referenceContainerForGroup = null;

    private DataPath m_scrollStartSelectedDataPath = null;

#if WINDOWS_PHONE
    private double m_manipulationStartScrollOffset = 0.0d;
#endif

    #endregion

    #region IScrollInfo implementation

    internal bool ForceInvalidateScrollInfo
    {
      get
      {
        return m_forceInvalidateIScrollInfo;
      }
    }

    internal void InvalidateScrollInfo()
    {
      ScrollViewer scrollViewer = this.ScrollOwner;

      if( scrollViewer != null )
      {
        scrollViewer.InvalidateScrollInfo();
      }
    }

    internal bool CanHorizontallyScroll
    {
      get;
      set;
    }

    internal bool CanVerticallyScroll
    {
      get;
      set;
    }

    internal double ExtentHeight
    {
      get
      {
        return m_extent.Height;
      }
      set
      {
        m_extent.Height = value;
      }
    }

    internal double ExtentWidth
    {
      get
      {
        return m_extent.Width;
      }
      set
      {
        m_extent.Width = value;
      }
    }

    internal double HorizontalOffset
    {
      get
      {
        return m_offset.X;
      }
      set
      {
        m_offset.X = value;
      }
    }

    internal ScrollViewer ScrollOwner
    {
      get
      {
        return m_scollOwner;
      }
      set
      {
        m_scollOwner = value;
      }
    }

    internal double VerticalOffset
    {
      get
      {
        return m_offset.Y;
      }
      set
      {
        m_offset.Y = value;
      }
    }

    internal double ViewportHeight
    {
      get
      {
        return m_viewport.Height;
      }
      set
      {
        if( m_viewport.Height != value )
        {
          m_viewport.Height = value;
        }
      }
    }

    internal double ViewportWidth
    {
      get
      {
        return m_viewport.Width;
      }
      set
      {
        m_viewport.Width = value;
      }
    }

    internal void LineDown()
    {
      this.SetOffset( PanelBase.LineSize, true, true );
    }

    internal void LineLeft()
    {
      this.SetOffset( PanelBase.LineSize, false, false );
    }

    internal void LineRight()
    {
      this.SetOffset( PanelBase.LineSize, true, false );
    }

    internal void LineUp()
    {
      this.SetOffset( PanelBase.LineSize, false, true );
    }

    internal Rect MakeVisible( UIElement visual, Rect rectangle )
    {
      throw new NotImplementedException();
    }

    internal void MouseWheelDown()
    {
      this.SetOffset( PanelBase.WheelSize, true, true );
    }

    internal void MouseWheelLeft()
    {
      this.SetOffset( PanelBase.WheelSize, false, false );
    }

    internal void MouseWheelRight()
    {
      this.SetOffset( PanelBase.WheelSize, true, false );
    }

    internal void MouseWheelUp()
    {
      this.SetOffset( PanelBase.WheelSize, false, true );
    }

    internal void PageDown()
    {
      this.SetOffset( m_itemsInViewport, true, true );
    }

    internal void PageLeft()
    {
      this.SetOffset( m_itemsInViewport, false, false );
    }

    internal void PageRight()
    {
      this.SetOffset( m_itemsInViewport, true, false );
    }

    internal void PageUp()
    {
      this.SetOffset( m_itemsInViewport, false, true );
    }

    internal void SetOffset( double jumpSize, bool jumpForward, bool verticalOffset )
    {
      double scrollOffset = jumpSize;

      // if there is a sticky header, we need to correct the 
      // jump so we do not scroll for more than an item
      if( ( this.LayoutManager.StickyHeader != null ) && ( this.IsCurrentDataPathAGroupBreak() ) )
      {
        if( jumpForward )
        {
          scrollOffset -= this.GetGroupHeaderProportion();
        }
        else
        {
          scrollOffset += this.GetGroupHeaderProportion();
        }
      }

      if( !jumpForward )
      {
        scrollOffset *= -1.0d;
      }

      if( verticalOffset )
      {
        this.SetVerticalOffset( this.ScrollManager.TargetIndex + scrollOffset, true );
      }
      else
      {
        this.SetHorizontalOffset( this.ScrollManager.TargetIndex + scrollOffset, true );
      }

    }

    internal void SetHorizontalOffset( double offset, bool fromIScrollInfoLineOrPageOperation = false )
    {
      m_forceInvalidateIScrollInfo = fromIScrollInfoLineOrPageOperation;

      ListBoxAnimationSettings animationSettings = this.ParentListBox.AnimationSettings;
      int duration = animationSettings.GetThumbScrollingDuration();
      IEasingFunction easingFunction = animationSettings.GetThumbScrollingEasingFunction();

      // Correct the offset to ensure it is in a valid range for IScrollInfo
      offset = Math.Min( this.ExtentWidth - this.ViewportWidth, offset );
      offset = Math.Max( 0, offset );

      if( this.HorizontalOffset == offset )
        return;

      // To ensure scrolled to item is fully visible
      offset = this.ScrollManager.RoundIndexToClosestItem( offset );

      this.ScrollManager.ScrollToIndex( offset,
        duration,
        easingFunction,
        null );
    }

    internal virtual void SetHorizontalOffsetCore( double offset )
    {
      this.SetVerticalOffsetCore( offset );
    }

    internal virtual void ForceHorizontalOffset( double offset )
    {
      this.ScrollManager.ForceScrollIndex( this.HorizontalOffset, offset );
      this.HorizontalOffset = offset;
      this.InvalidateScrollInfo();
    }

    internal void SetVerticalOffset( double offset, bool fromIScrollInfoLineOrPageOperation = false )
    {
      m_forceInvalidateIScrollInfo = fromIScrollInfoLineOrPageOperation;

      ListBoxAnimationSettings animationSettings = this.ParentListBox.AnimationSettings;
      int duration = animationSettings.GetThumbScrollingDuration();
      IEasingFunction easingFunction = animationSettings.GetThumbScrollingEasingFunction();

      // Correct the offset to ensure it is in a valid range for IScrollInfo
      offset = Math.Min( this.ExtentHeight - this.ViewportHeight, offset );
      offset = Math.Max( 0, offset );

      if( this.VerticalOffset == offset )
        return;

      // To ensure scrolled to item is fully visible
      offset = this.ScrollManager.RoundIndexToClosestItem( offset );

      this.ScrollManager.ScrollToIndex( offset,
        duration,
        easingFunction,
        null );
    }

    internal virtual void SetVerticalOffsetCore( double offset )
    {
      // Correct the offset to ensure it is in a valid range for IScrollInfo
      offset = Math.Min( offset, this.ExtentHeight - this.ViewportHeight );
      offset = Math.Max( 0, offset );

      if( offset != this.VerticalOffset )
      {
        long newValue = ( long )offset;
        long oldValue = ( long )this.VerticalOffset;
        long moveDifference = newValue - oldValue;

        if( moveDifference != 0 )
          this.EnumeratorHelper.Move( moveDifference );

        this.VerticalOffset = offset;

        if( this.ForceInvalidateScrollInfo )
        {
          this.InvalidateScrollInfo();
        }

        this.LayoutManager.UpdateViewportContent( this.RenderSize );
      }
    }

    internal virtual void ForceVerticalOffset( double offset )
    {
      this.ScrollManager.ForceScrollIndex( this.VerticalOffset, offset );
      this.VerticalOffset = this.ScrollManager.TargetIndex;
      this.InvalidateScrollInfo();
    }

    #endregion

    #region IScrollInfo Members

    bool IScrollInfo.CanHorizontallyScroll
    {
      get
      {
        return this.CanHorizontallyScroll;
      }
      set
      {
        this.CanHorizontallyScroll = value;
      }
    }

    bool IScrollInfo.CanVerticallyScroll
    {
      get
      {
        return this.CanVerticallyScroll;
      }
      set
      {
        this.CanVerticallyScroll = value;
      }
    }

    double IScrollInfo.ExtentHeight
    {
      get
      {
        return this.ExtentHeight;
      }
    }

    double IScrollInfo.ExtentWidth
    {
      get
      {
        return this.ExtentWidth;
      }
    }

    double IScrollInfo.HorizontalOffset
    {
      get
      {
        return this.HorizontalOffset;
      }
    }

    void IScrollInfo.LineDown()
    {
      this.LineDown();
    }

    void IScrollInfo.LineLeft()
    {
      this.LineLeft();
    }

    void IScrollInfo.LineRight()
    {
      this.LineRight();
    }

    void IScrollInfo.LineUp()
    {
      this.LineUp();
    }

    Rect IScrollInfo.MakeVisible( UIElement visual, Rect rectangle )
    {
      return this.MakeVisible( visual, rectangle );
    }

    void IScrollInfo.MouseWheelDown()
    {
      this.MouseWheelDown();
    }

    void IScrollInfo.MouseWheelLeft()
    {
      this.MouseWheelLeft();
    }

    void IScrollInfo.MouseWheelRight()
    {
      this.MouseWheelRight();
    }

    void IScrollInfo.MouseWheelUp()
    {
      this.MouseWheelUp();
    }

    void IScrollInfo.PageDown()
    {
      this.PageDown();
    }

    void IScrollInfo.PageLeft()
    {
      this.PageLeft();
    }

    void IScrollInfo.PageRight()
    {
      this.PageRight();
    }

    void IScrollInfo.PageUp()
    {
      this.PageUp();
    }

    ScrollViewer IScrollInfo.ScrollOwner
    {
      get
      {
        return this.ScrollOwner;
      }
      set
      {
        this.ScrollOwner = value;
      }
    }

    void IScrollInfo.SetHorizontalOffset( double offset )
    {
      this.SetHorizontalOffset( offset );
    }

    void IScrollInfo.SetVerticalOffset( double offset )
    {
      this.SetVerticalOffset( offset );
    }

    double IScrollInfo.VerticalOffset
    {
      get
      {
        return this.VerticalOffset;
      }
    }

    double IScrollInfo.ViewportHeight
    {
      get
      {
        return this.ViewportHeight;
      }
    }

    double IScrollInfo.ViewportWidth
    {
      get
      {
        return this.ViewportWidth;
      }
    }

    #endregion


    #region ISupportInitialize Members

    internal bool IsInitializing
    {
      get
      {
        return ( m_iSupportInitializeCount > 0 );
      }
    }


    void ISupportInitialize.BeginInit()
    {
      m_iSupportInitializeCount++;

      if( m_iSupportInitializeCount == 1 )
      {
        this.BeginInitCore();
      }
    }

    void ISupportInitialize.EndInit()
    {
      if( m_iSupportInitializeCount == 0 )
        return;

      m_iSupportInitializeCount--;

      if( m_iSupportInitializeCount == 0 )
      {
        this.EndInitCore();
      }
    }

    internal virtual void BeginInitCore()
    {
    }

    internal virtual void EndInitCore()
    {
    }

    #endregion

    #region ISupportInitialize replacement for WP7

#if WINDOWS_PHONE
    void OnPanelLoaded( object sender, RoutedEventArgs e )
    {
      this.EndInitCore();
    }
#endif

    #endregion
  }
}
